package com.study.common.utils;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;

/**
 * AES GCM 加密解密工具类 - 对称加密
 *
 * @author nizhihao
 * @version 1.0.0
 * @date 2022/1/19 19:52
 */
public class AesGcmUtils {

    private static final String AES = "AES";
    private static final String NO_PADDING = "AES/GCM/NoPadding";
    private static final int TAG_LENGTH_BIT = 128;

    private static final byte[] KEY = new byte[]{68, -109, -13, 127, 98, -83, 53, -62, -108, -15, -2, 96, -92, -84, -3, -85};
    private static final byte[] IV = new byte[]{-31, -27, -110, -24, 114, 37, -124, 124, 17, -39, 72, 104, 79, 59, 7, 13};
    private static final byte[] ADD = new byte[]{15, -117, 113, 101, 104, -4, -74, 0, -42, -109, 31, 3, -96, 103, -18, -118};

    /**
     * AES-128-GCM加密
     *
     * @param encryptData 加密解密长度
     * @param key         密钥
     * @param iv          IV 源缓冲器。复制缓冲区的内容以防止后续修改
     * @param add         增加的数据
     * @param tLen        身份验证标签长度（以位为单位）
     * @param mode        加密解密模式
     * @param padding     填充方式
     * @return 加密解密后的数据
     * @throws Exception 加密解密异常
     */
    private static byte[] aesGcm(byte[] encryptData,
                                 byte[] key,
                                 byte[] iv,
                                 byte[] add,
                                 int tLen,
                                 int mode,
                                 String padding) throws Exception {
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, AES);
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(tLen, iv);
        Cipher cipher = Cipher.getInstance(padding);
        cipher.init(mode, secretKeySpec, gcmParameterSpec);
        cipher.updateAAD(add);
        return cipher.doFinal(encryptData);
    }

    /**
     * AES-128-GCM加密(KEY/IV/ADD固定)
     *
     * @param encryptData 需要加密的数据
     * @return 加密后的数据
     * @throws Exception 异常
     */
    public static byte[] encrypt(byte[] encryptData) throws Exception {
        return aesGcm(encryptData, KEY, IV, ADD, TAG_LENGTH_BIT, Cipher.ENCRYPT_MODE, NO_PADDING);
    }

    /**
     * AES-128-GCM加密(KEY/IV/ADD固定)
     *
     * @param encryptData 需要加密的数据
     * @return 加密后的数据
     */
    public static String encrypt(String encryptData) {
        try {
            return RadixUtils.bytesToHex(aesGcm(encryptData.getBytes(StandardCharsets.UTF_8), KEY, IV, ADD, TAG_LENGTH_BIT, Cipher.ENCRYPT_MODE, NO_PADDING));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * AES-128-GCM加密
     *
     * @param encryptData 需要加密的数据
     * @param key         密钥
     * @param iv          IV源缓冲器。复制缓冲区的内容以防止后续修改
     * @param add         增加的数据
     * @return 加密后的数据
     * @throws Exception 异常
     */
    public static byte[] encrypt(byte[] encryptData,
                                 byte[] key,
                                 byte[] iv,
                                 byte[] add) throws Exception {
        return aesGcm(encryptData, key, iv, add, TAG_LENGTH_BIT, Cipher.ENCRYPT_MODE, NO_PADDING);
    }

    /**
     * AES-128-GCM加密
     *
     * @param encryptData 加密解密长度
     * @param key         密钥
     * @param iv          IV 源缓冲器。复制缓冲区的内容以防止后续修改
     * @param add         增加的数据
     * @param tLen        身份验证标签长度（以位为单位）
     * @param padding     填充方式
     * @return 加密解密后的数据
     * @throws Exception 加密解密异常
     */
    public static byte[] encrypt(byte[] encryptData,
                                 byte[] key,
                                 byte[] iv,
                                 byte[] add,
                                 int tLen,
                                 String padding) throws Exception {
        return aesGcm(encryptData, key, iv, add, tLen, Cipher.ENCRYPT_MODE, padding);
    }

    /**
     * AES-128-GCM解密(KEY/IV/ADD固定)
     *
     * @param decryptData 需要解密的数据
     * @return 解密后的数据
     * @throws Exception 异常
     */
    public static byte[] decrypt(byte[] decryptData) throws Exception {
        return aesGcm(decryptData, KEY, IV, ADD, TAG_LENGTH_BIT, Cipher.DECRYPT_MODE, NO_PADDING);
    }

    /**
     * AES-128-GCM解密(KEY/IV/ADD固定)
     *
     * @param decryptData 需要解密的数据
     * @return 解密后的数据
     * @throws Exception 异常
     */
    public static String decrypt(String decryptData) throws Exception {
        try {
            return new String(aesGcm(RadixUtils.hexToBytes(decryptData), KEY, IV, ADD, TAG_LENGTH_BIT, Cipher.DECRYPT_MODE, NO_PADDING), StandardCharsets.UTF_8);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * AES-128-GCM解密
     *
     * @param encryptData 需要加密的数据
     * @param key         密钥
     * @param iv          IV源缓冲器。复制缓冲区的内容以防止后续修改
     * @param add         增加的数据
     * @return 加密后的数据
     * @throws Exception 异常
     */
    public static byte[] decrypt(byte[] encryptData,
                                 byte[] key,
                                 byte[] iv,
                                 byte[] add) throws Exception {
        return aesGcm(encryptData, key, iv, add, TAG_LENGTH_BIT, Cipher.DECRYPT_MODE, NO_PADDING);
    }

    /**
     * AES-128-GCM加密
     *
     * @param encryptData 加密解密长度
     * @param key         密钥
     * @param iv          IV 源缓冲器。复制缓冲区的内容以防止后续修改
     * @param add         增加的数据
     * @param tLen        身份验证标签长度（以位为单位）
     * @param padding     填充方式
     * @return 加密解密后的数据
     * @throws Exception 加密解密异常
     */
    public static byte[] decrypt(byte[] encryptData,
                                 byte[] key,
                                 byte[] iv,
                                 byte[] add,
                                 int tLen,
                                 String padding) throws Exception {
        return aesGcm(encryptData, key, iv, add, tLen, Cipher.DECRYPT_MODE, padding);
    }

    public static boolean check(String current, String content) {
        String encrypt = encrypt(current);
        if (encrypt == null) {
            return false;
        }
        return encrypt.equals(content);
    }

    public static void main(String[] args) throws Exception {
        System.out.println(decrypt("a3e728b60f0cc34b28c2ccad50b4ce66fad4bc73e16bf74ddb"));
    }

}
